<template>
  <div class="dialogue-content-stretch">
    <div class="dialogue-content" :class="{ 'dialogue-content-center': !show }">
      <!-- 对话内容 -->
      <div ref="contentRef" class="dialogue-content-box">
        <template v-for="(item, itemIndex) in dialogueRecordsList" :key="item">
          <!-- 同一个回答，多条信息，切换展示 -->
          <TabsArrow :itemData="item" :dataList="item.con">
            <template #default="{ scope }">
              <div v-if="scope.textArr" :key="itemIndex + 'chart_row'" class="chart_row" :align="item.align">
                <div :class="{ group_info: item.align == 'left' }" class="chart_row_item">
                  <!-- 头像 -->
                  <span>
                    <img
                      v-if="item.align == 'right'"
                      :src="$getAssetsFile('icon-user.png')"
                      alt="用户"
                      width="32"
                      height="32"
                      style="margin-left: 13px; margin-top: 5px"
                    />
                    <img v-else :src="$getAssetsFile('icon-ai.png')" alt="AI" width="32" height="32" style="margin-right: 13px; margin-top: 5px" />
                  </span>
                  <!-- 操作单元～ -->
                  <div v-if="item.align == 'left'" class="module chart_row_item_text" color="blue">
                    <template v-for="textItem in scope.textArr" :key="textItem">
                      <div class="chart_row_item_text_box">
                        <!-- 不是最后一个 -->
                        <div v-if="itemIndex !== dialogueRecordsList.length - 1" class="asd222" @click="handleDialogueClick($event)">
                          <ExpandableContent :content="textItem.val" :baseShowShort="baseShowShort" :maxWordCount="110" :contentType="scope.type" />
                        </div>
                        <!-- 最后一个 -->
                        <div v-else class="asd111" @click="handleDialogueClick($event)">
                          <template v-if="Array.isArray(textItem.val)">
                            <template v-for="c_item in textItem.val">
                              <template v-if="c_item == '<el-divider></el-divider>'">
                                <el-divider :key="c_item" />
                              </template>
                              <template v-else-if="'file' === scope.type">
                                <el-link class="file-link" type="primary" @click="handleDown(c_item)">{{ c_item }}</el-link>
                              </template>
                              <template v-else>
                                <b :key="c_item" v-html="c_item" />
                              </template>
                            </template>
                          </template>
                          <template v-else>
                            <b v-html="textItem.val" />
                          </template>
                        </div>
                      </div>
                      <div class="clearfix module_head_top">
                        <span
                          v-if="dialogueStatus == 'end' && traceabilityStatus && !DetermineTextLegality(textItem)"
                          @click="handleTraceabilityEventSource(textItem, item)"
                        >
                          查看更多
                        </span>
                      </div>

                      <div class="clearfix module_head">
                        <div class="module_head_operation">
                          <template v-if="itemIndex == dialogueRecordsList.length - 1">
                            <el-link v-if="dialogueStatus == 'running'" :underline="false" class="li" @click="handleStopOutput(item.tag)">
                              <el-icon><i-ep-remove /></el-icon>
                              停止输出
                            </el-link>
                            <el-link
                              v-if="['end', 'stop', 'fail'].includes(dialogueStatus)"
                              :underline="false"
                              class="li"
                              @click="regenerate(item.tag)"
                            >
                              <el-icon><i-ep-refresh-right /></el-icon>
                              重新生成
                            </el-link>
                          </template>
                        </div>
                      </div>
                    </template>
                  </div>
                  <div v-else class="chart_row_item_text">
                    <template v-if="Array.isArray(scope.textArr)">
                      <!-- Arr -->
                      <template v-for="item in scope.textArr">
                        <template v-if="item == '<el-divider></el-divider>'">
                          <el-divider :key="item" />
                        </template>
                        <template v-else>
                          <p :key="item" v-html="item" />
                        </template>
                      </template>
                    </template>
                    <template v-else>
                      <!-- String -->
                      <b v-html="scope.textArr" />
                    </template>
                  </div>
                </div>
              </div>
              <!-- </template> -->
            </template>
          </TabsArrow>
          <!-- 三个问题 -->
          <div v-if="itemIndex == dialogueRecordsList.length - 1" :key="itemIndex" class="dialogue-content-prompt">
            <template v-if="item.con && item.con[item.con.length - 1]['questionArr']">
              <template v-if="item.con[item.con.length - 1]['questionArr'].length == 0 && dialogueStatus === 'running'">
                <div class="loading-current">
                  <span class="small-current" />
                  <span class="small-current" />
                  <span class="small-current" />
                  <span class="small-current" />
                  <span class="small-current" />
                </div>
              </template>
              <template v-else>
                <template v-for="textItem in item.con[item.con.length - 1]['questionArr']">
                  <span v-if="textItem" :key="textItem" @click="sendFullLibraryQAFromPropt(textItem)" v-html="textItem" />
                </template>
              </template>
            </template>
          </div>
        </template>
      </div>
      <!-- 底部的检索框 -->
      <div class="dialogue-content-search-content">
        <div class="dialogue-content-search-box">
          <el-input
            v-model="dialogueContent"
            type="textarea"
            :autosize="{ minRows: 2, maxRows: 4 }"
            resize="none"
            placeholder="在此输入您的问题，支持上传全文文件"
            :disabled="dialogueDisabled"
            @keydown.enter="handleKeydown($event)"
          />
          <div class="dialogue-content-search-btn-box">
            <span />
            <!-- 提交按钮 -->
            <el-button class="dialogue-content-search-btn" :disabled="dialogueDisabled" @click="handleSend()">
              <img :src="$getAssetsFile('btn-submit.png')" alt="提交" />
            </el-button>
          </div>
        </div>
      </div>
    </div>
    <div :class="{ 'dialogue-content-stretch-collapsed': show }" />
  </div>
</template>

<script setup>
import { queryEntity, queryVectorData, queryStreamMulQA, queryStreamQA, getFilePath, downloadFile } from "@/api/ai";
import TabsArrow from "@/components/TabsArrow";
import ExpandableContent from "@/components/ExpandableContent";
import { map, trim, find, remove, cloneDeep, isEmpty, last } from "lodash-es";
import {
  mergeDialogue,
  saveRightDialogueRecord,
  saveLeftDialogueRecord,
  modifyLeftDialogueRecord,
  delLeftDialogueRecord,
  getCurrDialogue,
  getDialogueRecords,
  getVectorData
} from "@/hooks/ai-dialogue";

const props = defineProps({
  show: {
    type: Boolean,
    default: true
  },
  loading: {
    type: Boolean,
    default: false
  },
  // 向量数据第几条数据
  sortNum: {
    type: Number,
    default: 1
  }
});
const emits = defineEmits(["update:loading", "update:show", "update:sortNum"]);

// 问答按钮是否禁用
let dialogueDisabled = ref(false);
// 输入框内容
let dialogueContent = ref("");
// 记录会话输出状态。详见 @utils/fetch-event-source.js
let dialogueStatus = ref("end");
// 终止请求控制器。问答、溯源和三个问题都用这个控制器。
let abortController = ref(null);
// 是否显示全部 - 默认问答的展开显示
const baseShowShort = ref(true);
// 是否可以查看溯源
let traceabilityStatus = ref(false);

// 当前会话
const currDialogue = computed(() => {
  return getCurrDialogue();
});
// 会话消息内容
let dialogueRecordsList = ref([]);
// 右侧对话加载效果
let vectorLoading = computed({
  get() {
    return props.loading;
  },
  set(val) {
    emits("update:loading", val);
  }
});

watch(
  () => currDialogue,
  (val) => {
    nextTick(() => {
      dialogueDisabled.value = val.flag;
      dialogueRecordsList.value = cloneDeep(getDialogueRecords());
      val?.time && handleInit(val.value);
      scrollBottom();
    });
  },
  {
    deep: true
  }
);

onMounted(() => {
  nextTick(() => {
    dialogueDisabled.value = currDialogue.value.flag;
    dialogueRecordsList.value = cloneDeep(getDialogueRecords());
    handleInit(currDialogue.value);
  });
});

// 初始化
const handleInit = (val) => {
  let { time, title, flag } = val;
  if (flag) {
    return;
  }
  // 设置问答已经处理了
  mergeDialogue({ flag: true });
  handleSend(title, time);
};

// 问答对话，query问题
const handleSend = async (query, tag) => {
  let queryContent = trim(query || dialogueContent.value);
  if (!queryContent) {
    ElMessage.error("请提出您的想法。");
    return;
  }
  dialogueDisabled.value = true;

  // 不是重新生成，则添加问题到历史记录
  if (!tag) {
    saveDialogue(queryContent);
  }

  let timestamp = tag || Date.now();
  // 调用接口获取实体
  const flag = await handleEntity(queryContent, timestamp);
  if (!flag) {
    // 调向量数据库接口获取数据
    await handleQueryVectorData(queryContent);

    // 根据向量数据获取问答结果
    await handleEventSource(queryContent, timestamp);
  }
  dialogueDisabled.value = false;
};

// 重新生成
const regenerate = (tag) => {
  // 先删除上一轮的输出结果
  remove(dialogueRecordsList.value, ["tag", tag]);
  // 删除本地缓存
  delLeftDialogueRecord(tag);
  handleSend(currDialogue.value.lastQuestion, tag);
};

// 停止输出
const handleStopOutput = (tag) => {
  abortController.value.abort();
  // 手动结束也要保存对话记录
  let dialogue = find(dialogueRecordsList.value, ["tag", tag]);
  saveLeftDialogueRecord(dialogue);

  dialogueStatus.value = "stop";
  dialogueDisabled.value = false;
};

// 保存问题到本地
const saveDialogue = (val) => {
  let dialogue = saveRightDialogueRecord(val);
  dialogueRecordsList.value.push(dialogue);
  // 清空输入框
  dialogueContent.value = "";
  // 滚动至底部
  scrollBottom();
};

// 获取实体并返回下载文件地址
const handleEntity = async (val, timestamp) => {
  let flag = false;
  let entity = null;
  await queryEntity({ question: val }).then((res) => {
    entity = JSON.parse(res);
  });
  if (!entity || entity.data.length === 0) {
    return;
  }
  await getFilePath({ ids: entity.data }).then((res) => {
    // 返回false说明文件都不存在
    if (!res.data) {
      return;
    }

    let dialogue = {
      tag: timestamp,
      align: "left",
      con: [
        {
          activeName: 1,
          textArr: [{ val: [res.data] }],
          questionArr: [],
          type: "file"
        }
      ]
    };
    dialogueRecordsList.value.push(dialogue);
    // 持久化存储会话
    addLeftDialogueRecord({ dialogue, tag: timestamp });
    flag = true;
  });

  // 滚动至底部
  scrollBottom();

  return flag;
};

// 获取向量数据库数据
const handleQueryVectorData = async (val) => {
  vectorLoading.value = true;
  let param = {
    question: JSON.stringify(val),
    page_size: 5
  };
  await queryVectorData(param)
    .then((res) => {
      let data = JSON.parse(res)?.data ?? [
        {
          id: "",
          name_ch: "当前问题没有对应的相关文献。",
          activeName: -2, // -2 走下面遍历之后会变成-1，不影响右侧抽屉数据展示
          describe: "无",
          systemsing: ""
        }
      ];
      data.forEach((item, index) => {
        item.activeName = index * 1 + 1;
      });
      modifyLeftDialogueRecord({ vectorData: data });
      vectorLoading.value = false;
    })
    .catch((err) => {
      vectorLoading.value = false;
    });
};

// 持续输出问答结果。
const handleEventSource = async (question, timestamp) => {
  let textArrVal = ref([]);
  // 构建左侧会话数据
  let data = {
    tag: timestamp,
    align: "left",
    con: [
      {
        activeName: 1,
        textArr: [{ val: textArrVal.value }],
        questionArr: []
      }
    ]
  };
  let params = handleEventSourceParams(question, import.meta.env.VITE_APP_BASE_PROMPT_QUESTIONSANDANSWERS);
  await queryStreamQA(params, (res) => {
    if (res.status === "connect") {
      abortController.value = res.abortController;
      return;
    }
    // 缓存状态
    mergeDialogue({ status: res.status });

    let resData = res.data.replaceAll("\n", "<br />");
    if (res.status === "fail") {
      dialogueStatus.value = "fail";
      textArrVal.value.push(resData);
      dialogueRecordsList.value.push(data);
      saveLeftDialogueRecord(data);
      return;
    }
    // 处理问答结果。查记录是否已存在
    let dialogue = find(dialogueRecordsList.value, ["tag", timestamp]);
    if (res.status === "end") {
      // 持续输出结束后再持久化存储会话，中途只页面展示，不操作缓存
      saveLeftDialogueRecord(dialogue);

      // 查询三个问题
      async () => await handleThreeQuestions(timestamp);
      dialogueDisabled.value = false;
      return;
    }
    dialogueStatus.value = "running";
    if (!dialogue) {
      textArrVal.value.push(resData);
      dialogueRecordsList.value.push(data);
    } else {
      dialogueRecordsList.value.forEach((item) => {
        if (res.data && item.tag === timestamp) {
          item.con = [
            {
              activeName: 1,
              textArr: [{ val: [resData] }], //
              questionArr: []
            }
          ];
        }
      });
    }
    // 滚动至底部
    scrollBottom();
  });
};

// 持续输出溯源问答结果
const handleTraceabilityEventSource = (textItem, item) => {
  if (dialogueStatus.value == "running") {
    return ElMessage.warning("对话进行中，请稍后再试");
  }

  let originTextItemVal = [];
  let params = handleEventSourceParams(currDialogue.value.lastQuestion, import.meta.env.VITE_APP_BASE_PROMPT_INVESTIGATE);
  queryStreamMulQA(params, (res) => {
    if (res.status === "connect") {
      abortController.value = res.abortController;
      textItem.val.push("<br/>", "<el-divider></el-divider>", "<b class='btn-traceability'>知识溯源：</b>", "<br/>");
      originTextItemVal = cloneDeep(textItem.val);
      return;
    }
    if (res.status === "fail") {
      dialogueStatus.value = "fail";
      return;
    }
    // 缓存状态
    mergeDialogue({ status: res.status });
    if (res.status === "end") {
      dialogueStatus.value = "end";
      traceabilityStatus.value = false;
      // 持久化存储会话
      modifyLeftDialogueRecord({ dialogue: textItem.val, tag: item.tag });
      return;
    }
    dialogueStatus.value = "running";
    textItem.val = originTextItemVal.concat(purifyMsgData(res.data, item.tag));
    // 滚动至底部
    scrollBottom();
  });
};

// 持续输出三个问题
const handleThreeQuestions = async (timestamp) => {
  let type = import.meta.env.VITE_APP_BASE_PROMPT_THREEQUESTIONS;
  let params = handleEventSourceParams("", type);
  if (isEmpty(params.abstracts)) {
    return;
  }
  await queryStreamQA(params, (res) => {
    if (res.status === "connect") {
      abortController.value = res.abortController;
      return;
    }
    if (res.status === "fail") {
      dialogueStatus.value = "fail";
      return;
    }
    // 缓存状态
    mergeDialogue({ status: res.status });
    let dialogue = find(dialogueRecordsList.value, ["tag", timestamp]);
    if (res.status === "end") {
      dialogueStatus.value = "end";
      // 开启溯源查看
      traceabilityStatus.value = true;

      // 持久化存储会话
      modifyLeftDialogueRecord({ dialogue: dialogue, tag: timestamp });
      return;
    }
    dialogueStatus.value = "running";
    let resData = res.data.replaceAll("\n", "<br />").replaceAll(type, "");
    let questionArr = resData.split("<br />");
    if (dialogue?.align == "left") {
      const lastCon = last(dialogue.con);
      lastCon["questionArr"] = questionArr.splice(0, 3);
    }
    // 滚动至底部
    scrollBottom();
  });
};

// 三个相关问题点击提交的时候
const sendFullLibraryQAFromPropt = (item) => {
  let str = item;
  str = str.replace(/^\d+[:]/, "").trim(); // 去掉开头的1. 和左右空格
  handleSend(str);
};

// 处理问答参数。val传入不同问答类型
const handleEventSourceParams = (question, val) => {
  // 格式：{问题,向量数据摘要}。从缓存中取向量数据
  const vectorData = getVectorData();
  let abstracts = map(vectorData, (item) => {
    let temp = [];
    item?.name_ch && temp.push(item.name_ch);
    item?.name_en && temp.push(item.name_en);
    item?.describe && temp.push(item.describe);
    return temp.join(";\t");
  });
  return {
    question: question + val,
    abstracts: abstracts
  };
};

// 将返回的溯源的信息，处理
const purifyMsgData = (msgData, dateTag) => {
  const regex = /<a\s+href=(['"])#(\d+)\1\s*\/?>|<a\s+href=(['"])#(\d+)\3>\s*<\/a>/g;
  const regex2 = /<a\s+href=(['"])#(\d+)\1\s*>.*?<\/a>/g;
  const regex3 = /<a\s+herf=(['"])#(\d+)\1\s*\/?>|<a\s+herf=(['"])#(\d+)\3>\s*<\/a>/g;
  const regex4 = /<a\s+herf=(['"])#(\d+)\1\s*\/?>|<a\s+herf=(['"])#(\d+)\3>\s*<\/a>/g;

  let dataConPageNum = 1;
  if (dateTag) {
    dialogueRecordsList.value.forEach((res) => {
      if (res.tag == dateTag) {
        dataConPageNum = res.con.length;
      }
    });
  }
  if (regex.exec(msgData)) {
    msgData = msgData.replaceAll(regex, `<em class='icon-badge' data-val='${dataConPageNum},$2,${dateTag}'>$2</em>`);
  }
  if (regex2.exec(msgData)) {
    msgData = msgData.replaceAll(regex2, `<em class='icon-badge' data-val='${dataConPageNum},$2,${dateTag}'>$2</em>`);
  }
  if (regex3.exec(msgData)) {
    msgData = msgData.replaceAll(regex3, `<em class='icon-badge' data-val='${dataConPageNum},$2,${dateTag}'>$2</em>`);
  }
  if (regex4.exec(msgData)) {
    msgData = msgData.replaceAll(regex4, `<em class='icon-badge' data-val='${dataConPageNum},$2,${dateTag}'>$2</em>`);
  }

  msgData = msgData.replaceAll("<p/>", "<em class='icon-circle'></em>");
  return msgData;
};

// 判断溯源按钮是否显示 - 文本合法性判断
const DetermineTextLegality = (textItem) => {
  const IllegalText = ["很抱歉", "我无法确定", "我没有能力", "无法确定", "没有能力", "知识溯源："];
  const textItemStr = textItem.val.join("");
  // 判断字符串textItem 是否包含非法文本 IllegalText为非法文本
  const result = containsIllegalText(textItemStr, IllegalText);
  return result;
};

// 溯源与右侧向量数据映射
const handleDialogueClick = async (e) => {
  const targetObjId = e.target.classList[0];
  // 判断是否点击图片
  if (targetObjId == "icon-badge") {
    const [pageNum, id, tag] = e.target.dataset.val.split(",");
    // 显示右侧抽屉
    emits("update:show", true);
    emits("update:sortNum", +id);
  }
};

const containsIllegalText = (text, illegalList) => {
  for (const illegalItem of illegalList) {
    if (text.includes(illegalItem)) {
      return true;
    }
  }
  return false;
};

const handleDown = (name) => {
  downloadFile({ name }).then((res) => {
    const blob = new Blob([res], { type: "application/octet-stream" });
    const url = window.URL.createObjectURL(blob);
    const link = document.createElement("a");
    link.href = url;
    link.download = name;
    link.click();
    window.URL.revokeObjectURL(url);
  });
};

// 滚动条滚动至底部
let contentRef = ref();
const scrollBottom = () => {
  setTimeout(() => {
    nextTick(() => {
      let scrollEl = contentRef.value;
      scrollEl.scrollTo({ top: scrollEl.scrollHeight, behavior: "smooth" });
    });
  }, 100);
};

// 问答框，回车操作处理
const handleKeydown = (event) => {
  // 检查是否按下回车键
  if (event.keyCode === 13) {
    // 检查是否同时按下了 Shift 键
    if (event.shiftKey) {
      // 执行换行操作
    } else {
      // 执行提交操作
      handleSend();
      event.preventDefault();
    }
  }
};
</script>

<style lang="scss" scoped>
:deep(.dialogue-content-search-box .el-textarea__inner) {
  border: none;
  padding: 0;
  margin-bottom: 10px;
  box-shadow: none;
  font-size: 16px;
}
:deep(.file-link span) {
  position: relative;
}
</style>
